package cn.zzdt4j.config.springboot.starter.monitor;

import cn.hutool.core.util.StrUtil;
import cn.zzdt4j.common.executor.ThreadFactoryBuilder;
import cn.zzdt4j.common.executor.ThreadPoolExecutorRegistry;
import cn.zzdt4j.common.extension.spi.ServiceLoaderRegistry;
import cn.zzdt4j.core.config.ApplicationContextHolder;
import cn.zzdt4j.threadpool.dynamic.model.config.properties.BootstrapConfigProperties;
import cn.zzdt4j.threadpool.dynamic.model.config.properties.MonitorProperties;
import cn.zzdt4j.threadpool.monitor.api.DynamicThreadPoolMonitor;
import cn.zzdt4j.threadpool.monitor.api.ThreadPoolMonitor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;

import java.util.*;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Thread-pool monitor executor
 *
 * @author by <a href="mailto:ligang941012@gmail.com">gang.Li</a>
 * @since 2024/5/13 23:55
 */
@Slf4j
@RequiredArgsConstructor
public class ThreadPoolMonitorExecutor implements ApplicationRunner, DisposableBean {

    private final BootstrapConfigProperties properties;

    private ScheduledThreadPoolExecutor collectScheduledThreadPoolExecutor;

    private List<ThreadPoolMonitor> threadPoolMonitors;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        final MonitorProperties monitor = properties.getMonitor();
        if (monitor == null
                || !monitor.getEnable()
                || StrUtil.isBlank(monitor.getThreadPoolTypes())
                || StrUtil.isBlank(monitor.getCollectTypes())) {
            return;
        }
        log.info("Start monitoring the running status of dynamic thread-pool.");
        threadPoolMonitors = new ArrayList<>();
        collectScheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(
                1,
                ThreadFactoryBuilder.builder().daemon(true).namePrefix("client.scheduled.collect.data").build());
        // Get dynamic thread pool monitoring component.
        final List<String> collectTypes = Arrays.asList(monitor.getCollectTypes().split(","));
        ApplicationContextHolder.getBeanOfType(ThreadPoolMonitor.class).forEach((beanName, bean) -> threadPoolMonitors.add(bean));
        final Collection<DynamicThreadPoolMonitor> dynamicThreadPoolMonitors =
                ServiceLoaderRegistry.getSingletonServiceInstances(DynamicThreadPoolMonitor.class);
        dynamicThreadPoolMonitors.stream().filter(each -> collectTypes.contains(each.getType())).forEach(each -> threadPoolMonitors.add(each));
        // Execute dynamic thread-pool monitoring component.
        collectScheduledThreadPoolExecutor.scheduleWithFixedDelay(
                this::scheduleRunnable,
                monitor.getInitialDelay(),
                monitor.getCollectInterval(),
                TimeUnit.MILLISECONDS);
        if (ThreadPoolExecutorRegistry.getThreadPoolExecutorSize() > 0) {
            log.info("Dynamic thread pool: [{}]. The dynamic thread pool starts data collection and reporting.", ThreadPoolExecutorRegistry.getThreadPoolExecutorSize());
        }
    }

    private void scheduleRunnable() {
        threadPoolMonitors.forEach(each -> {
            try {
                each.collect();
            } catch (Exception ex) {
                log.error("Error monitoring thr running status of dynamic thread pool. Type: {}", each.getType(), ex);
            }
        });
    }

    @Override
    public void destroy() throws Exception {
        Optional.ofNullable(collectScheduledThreadPoolExecutor).ifPresent(ScheduledThreadPoolExecutor::shutdown);
    }
}
